﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebPartPages;
using System.Xml;
using System.Web.UI.WebControls.WebParts;
using Microsoft.PKS;

namespace PKS.UserInterfaceHandler
{
    class WebPartPages
    {
        #region Member Variables
        private Logger logger;
        private SPFeatureReceiverProperties properties;
        #endregion

        #region Constructor
        /// <summary>
        /// Constructor that sets the receiver properties
        /// and logger passed in by the feature receiver
        /// </summary>
        /// <param name="properties">Receiver properties</param>
        /// <param name="logger">Logger instance</param>
        internal WebPartPages(SPFeatureReceiverProperties properties, Logger logger)
        {
            this.properties = properties;
            this.logger = logger;
        }
        #endregion

        #region Web Part Routines
        /// <summary>
        /// Imports a webpart based on the webPartPath
        /// add adds it to the current page via the
        /// web part manager
        /// </summary>
        /// <param name="webPartManager">SharePoint web part manager</param>
        /// <param name="webPartPath">Path to the web part import file</param>
        /// <param name="zoneId">Id of zone where web part is added</param>
        /// <param name="webPartId">Id of web part. Used to identify the web part in
        /// connections</param>
        /// <param name="zoneIndex">Index of web part within zone</param>
        /// <returns>String of error message if import didn't work</returns>
        private string CreateWebPartOnPage(
            SPLimitedWebPartManager webPartManager,
            string webPartPath,
            string zoneId,
            string webPartId,
            int zoneIndex)
        {
            System.Diagnostics.Debug.Assert(System.IO.File.Exists(webPartPath));
            System.Diagnostics.Debug.Assert(zoneIndex >= 0);
            System.Diagnostics.Debug.Assert(zoneId != null);
            System.Diagnostics.Debug.Assert(zoneId.Length > 0);
            System.Diagnostics.Debug.Assert(webPartId != null);
            System.Diagnostics.Debug.Assert(webPartId.Length > 0);
            System.Diagnostics.Debug.Assert(webPartManager != null);

            string retValue = string.Empty;

            // Open the web part description file
            XmlReader reader = XmlTextReader.Create(webPartPath);

            try
            {
                // Import the web part
                string errorMessage = string.Empty;
                System.Web.UI.WebControls.WebParts.WebPart webPart = webPartManager.ImportWebPart(reader, out errorMessage);

                // If the import worked, add the web part
                if (webPart != null && errorMessage == null)
                {
                    // We don't allow any chrome
                    webPart.ChromeType = System.Web.UI.WebControls.WebParts.PartChromeType.None;

                    // Set the id so the web part can be manipulated via connections
                    webPart.ID = webPartId;

                    // Add the web part to the page
                    webPartManager.AddWebPart(webPart, zoneId, zoneIndex);
                }
                else
                {
                    // Set the return value to the error message returned 
                    //by the import call
                    retValue = errorMessage;
                }
            }
            catch (WebPartPageUserException ex)
            {
                // Return the exception message as the return value
                retValue = ex.Message;
            }
            finally
            {
                // Close the reader
                if (reader != null && reader.ReadState != ReadState.Closed)
                {
                    reader.Close();
                }
            }

            return retValue;
        }

        /// <summary>
        /// Creates web parts for the current page
        /// and creates any connections based on the 
        /// information passed in.
        /// </summary>
        /// <param name="webPartManager"></param>
        /// <param name="webPartFolder"></param>
        /// <param name="pageNode"></param>
        /// <param name="nsm"></param>
        private void CreateWebPartsForPage(
            SPLimitedWebPartManager webPartManager,
            string webPartFolder,
            XmlNode pageNode,
            XmlNamespaceManager nsm)
        {
            System.Diagnostics.Debug.Assert(System.IO.Directory.Exists(webPartFolder));
            System.Diagnostics.Debug.Assert(webPartManager != null);
            System.Diagnostics.Debug.Assert(pageNode != null);
            System.Diagnostics.Debug.Assert(nsm != null);

            XmlAttribute webPartFileNameAttribute;
            XmlAttribute webPartZoneAttribute;
            XmlAttribute webPartIdAttribute;
            XmlAttribute webPartOrderAttribute;

            int webPartIndex;
            string webPartPath;

            bool partAdded = true;
            string errorMessage = string.Empty;

            XmlNodeList webPartNodes = pageNode.SelectNodes("sp:WebPart", nsm);

            // Walk through the webpart nodes and
            // add them to the page
            foreach (XmlNode webPartNode in webPartNodes)
            {
                partAdded = false;

                // Get the web part attributes
                webPartFileNameAttribute = webPartNode.Attributes["FileName"];
                webPartZoneAttribute = webPartNode.Attributes["WebPartZoneId"];
                webPartIdAttribute = webPartNode.Attributes["WebPartId"];
                webPartOrderAttribute = webPartNode.Attributes["WebPartOrder"];

                // Make sure we have a valid file name attribute
                if (webPartFileNameAttribute != null && webPartFileNameAttribute.InnerText != null && webPartFileNameAttribute.InnerText.Length > 0)
                {
                    // Make sure we have a valid web part zone attribute
                    if (webPartZoneAttribute != null && webPartZoneAttribute.InnerText != null && webPartZoneAttribute.InnerText.Length > 0)
                    {
                        // Make sure we have a valid web part id attribute
                        if (webPartIdAttribute != null && webPartIdAttribute.InnerText != null && webPartIdAttribute.InnerText.Length > 0)
                        {
                            // Make sure we have a valid web part order attribute
                            if (webPartOrderAttribute != null && webPartOrderAttribute.InnerText != null && webPartOrderAttribute.InnerText.Length > 0)
                            {
                                // The order attribute exists. Need to make sure it is a valid integer
                                if (int.TryParse(webPartOrderAttribute.InnerText, out webPartIndex))
                                {
                                    // The order is a valid integer, but it has to be 0 or greater
                                    if (webPartIndex >= 0)
                                    {
                                        // Calculate the path to the web part description file
                                        webPartPath = webPartFolder + webPartFileNameAttribute.InnerText;

                                        // Make sure the file exists
                                        if (System.IO.File.Exists(webPartPath))
                                        {
                                            // Create the webpart on the page
                                            errorMessage = CreateWebPartOnPage(
                                                webPartManager, 
                                                webPartPath, 
                                                webPartZoneAttribute.InnerText, 
                                                webPartIdAttribute.InnerText, 
                                                webPartIndex
                                                );
                                            
                                            // If the error message is empty then the 
                                            // web part add worked.
                                            if (errorMessage.Length == 0)
                                            {
                                                partAdded = true;
                                            }
                                        }
                                        else
                                        {
                                            errorMessage = "Web Part File doesn't exist - " + webPartFileNameAttribute.InnerText;
                                        }
                                    }
                                    else
                                    {
                                        errorMessage = "WebPartOrder needs to be a positive number";
                                    }
                                }
                                else
                                {
                                    errorMessage = "WebPartOrder needs to be a positive integer";
                                }
                            }
                            else
                            {
                                errorMessage = "WebPartOrder attribute is missing or is empty";
                            }
                        }
                        else
                        {
                            errorMessage = "WebPartId attribute is missing or is empty";
                        }
                    }
                    else
                    {
                        errorMessage = "WebPartZoneId attribute is missing or is empty";
                    }
                }
                else
                {
                    errorMessage = "FileName attribute is missing";
                }

                // If we didn't add the web part we need to document that
                if (!partAdded)
                {
                    // Log the error message. Include the page and any of the attributes that 
                    // can be used.
                    LogWebPartAddedError(pageNode, webPartNode, errorMessage);
                }
            }

            // Create any web part connections for the page
            CreateWebPartConnectionsForPage(webPartManager, pageNode, nsm);
        }


        /// <summary>
        /// Creates any web part connections on the page
        /// </summary>
        /// <param name="webPartManager">Web part manager</param>
        /// <param name="pageNode">Node from configuration file that represents the current page</param>
        /// <param name="nsm">Namespace manager for configuration file</param>
        private void CreateWebPartConnectionsForPage(
            SPLimitedWebPartManager webPartManager,
            XmlNode pageNode,
            XmlNamespaceManager nsm)
        {
            System.Diagnostics.Debug.Assert(webPartManager != null);
            System.Diagnostics.Debug.Assert(pageNode != null);
            System.Diagnostics.Debug.Assert(nsm != null);
            
            // Find all of the connection configurations within the page
            XmlNodeList webPartConnectionNodes = pageNode.SelectNodes("sp:WebPartConnection", nsm);

            ConsumerConnectionPointCollection ccc;
            ProviderConnectionPointCollection ppp;

            System.Web.UI.WebControls.WebParts.WebPart consumerPart;
            System.Web.UI.WebControls.WebParts.WebPart providerPart;

            SPWebPartConnection webPartConnection;
            XmlAttribute consumerIdAttribute;
            XmlAttribute providerIdAttribute;
            XmlAttribute fieldNameAttribute;

            foreach (XmlNode connectionNode in webPartConnectionNodes)
            {
                // Get the attributes
                consumerIdAttribute = connectionNode.Attributes.GetNamedItem("ConsumerId") as XmlAttribute;
                providerIdAttribute = connectionNode.Attributes.GetNamedItem("ProviderId") as XmlAttribute;
                fieldNameAttribute = connectionNode.Attributes.GetNamedItem("FieldName") as XmlAttribute;

                // Make sure we have valid attributes
                if (consumerIdAttribute != null && providerIdAttribute != null && fieldNameAttribute != null)
                {
                    // Retrieve the web parts from the page
                    consumerPart = webPartManager.WebParts[consumerIdAttribute.InnerText];
                    providerPart = webPartManager.WebParts[providerIdAttribute.InnerText];

                    // Create the connection points
                    ccc = webPartManager.GetConsumerConnectionPoints(consumerPart);
                    ppp = webPartManager.GetProviderConnectionPoints(providerPart);

                    // We have to create a RowToFieldTransformer instance
                    RowToFieldTransformer t = new RowToFieldTransformer();
                    t.FieldName = fieldNameAttribute.InnerText;

                    // Create a connection
                    webPartConnection = webPartManager.SPConnectWebParts(providerPart, ppp[0], consumerPart, ccc[0], t);
                    
                    // And add it to the list of connections
                    webPartManager.SPWebPartConnections.Add(webPartConnection);
                }
            }
        }

        private void LogWebPartAddedError(XmlNode pageNode, XmlNode webPartNode, string errorMessage)
        {
        }

        #endregion

        #region Page Routines
        /// <summary>
        /// Removes an existing page from the web
        /// </summary>
        /// <param name="pubWeb">Web to be searched</param>
        /// <param name="pageName">Name of page to remove if it exists within the web. ".aspx" is requited.</param>
        private bool RemoveExistingPage(PublishingWeb pubWeb, string pageName)
        {
            // Assume the page can be removed. In the event it doesn't exist
            // we want the creation to proceed.
            bool retValue = true;
            
            // Get all of the pages associated with the web
            PublishingPageCollection pages = pubWeb.GetPublishingPages();

            // Walk through the pages looking for a matching name
            foreach (PublishingPage existingPage in pages)
            {
                // Check for a matching name
                if (string.Compare(existingPage.Name, pageName, true) == 0)
                {
                    // Remove the page from the web
                    try
                    {
                        existingPage.ListItem.Delete();

                    }
                    catch (SPException ex)
                    {
                        if ((uint)ex.ErrorCode == 0x81020089)
                        {
                            logger.Write(System.Diagnostics.EventLogEntryType.Error, "Could not the following page because it is currently the welcome page: " + existingPage.Name);
                        }
                        else
                        {
                            logger.Write(System.Diagnostics.EventLogEntryType.Error, String.Format("Could not delete page: {0} due to the following SharePoint Exception: {1}", existingPage.Name, ex.Message));
                        }
                        retValue = false;
                    }
                    break;
                }
            }

            return retValue;
        }

        /// <summary>
        /// Create a new page in the web specified, based on the layout specified
        /// along with the page configuration node information
        /// </summary>
        /// <param name="web">Web page is being created in</param>
        /// <param name="layout">Layout page that the content page is based upon</param>
        /// <param name="pageNode">Page configuration node</param>
        /// <param name="nsm">Namespace manager for the page configuration node</param>
        /// <param name="webPartFolder">Folder where the webpart configuration files reside</param>
        private void CreatePage(SPWeb web, PageLayout layout, XmlNode pageNode, XmlNamespaceManager nsm, string webPartFolder)
        {
            System.Diagnostics.Debug.Assert(web != null);
            System.Diagnostics.Debug.Assert(layout != null);
            System.Diagnostics.Debug.Assert(nsm != null);
            System.Diagnostics.Debug.Assert(webPartFolder != null);
            System.Diagnostics.Debug.Assert(webPartFolder.Length > 0);

            if (pageNode != null)
            {
                XmlAttribute nameAttribute = pageNode.Attributes["Name"];
                
                // Get the webparts in the 
                XmlNodeList webParts = pageNode.SelectNodes("sp:WebPart", nsm);
                
                // Get the publishing interface for the current web
                PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(web);

                if (pubWeb != null)
                {
                    if (nameAttribute.InnerText.Length > 0 && layout != null)
                    {
                        if (webPartFolder != null && webPartFolder.Length > 0 && System.IO.Directory.Exists(webPartFolder))
                        {
                            // Remove the page if it currently exists. If there was an error then we don't 
                            // create a new page.
                            if (RemoveExistingPage(pubWeb, nameAttribute.InnerText))
                            {
                                // Create the page 
                                PublishingPage newPage = pubWeb.GetPublishingPages().Add(nameAttribute.InnerText, layout);

                                if (newPage != null)
                                {
                                    // Create the web parts for the page
                                    CreateWebPartsForPage(
                                        web.GetLimitedWebPartManager(newPage.Url, System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared),
                                        webPartFolder,
                                        pageNode,
                                        nsm);

                                    newPage.Update();
                                    newPage.CheckIn("");
                                    newPage.ListItem.File.Publish("");
                                }
                            }
                            else
                            {
                                logger.Write(System.Diagnostics.EventLogEntryType.Error, string.Format("The following page was not created as the page could not be removed. See the previous log entry for more details: {0}", nameAttribute.InnerText));
                            }
                        }
                    }
                }
            }
        }

        internal void CreatePages()
        {
            if (properties != null)
            {
                using (SPSite site = (SPSite)properties.Feature.Parent)
                {
                    if (site != null)
                    {
                        using (SPWeb web = site.OpenWeb())
                        {
                            string xmlDocumentName = @"webpages.xml";
                            string featurePath = properties.Definition.RootDirectory;
                            string baseWebPartPath = featurePath + @"\Pages\";
                            string configFilePath = baseWebPartPath + xmlDocumentName;

                            // Make sure we can find the configuration file
                            if (System.IO.File.Exists(configFilePath))
                            {
                                XmlDocument doc = new XmlDocument();

                                try
                                {
                                    // Load the configuration file
                                    doc.Load(configFilePath);

                                    // Add the config file namespace to the namespace manager
                                    XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable);
                                    nsm.AddNamespace("sp", "http://schemas.microsoft.com/sharepoint/");
                                                                        
                                    // Make sure this is a publishing-enabled web
                                    if (PublishingWeb.IsPublishingWeb(web))
                                    {
                                        // Get the publishing web from the web
                                        PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(web);

                                        // Find the layout page that we're looking for
                                        PageLayout[] layouts = pubWeb.GetAvailablePageLayouts();
                                        PageLayout targetLayout = null;

                                        // Get the module line from the config file
                                        XmlNode moduleNode = doc.SelectSingleNode(@"sp:Elements/sp:Module", nsm);

                                        if (moduleNode != null)
                                        {
                                            // Get the layout page name
                                            XmlAttribute layoutAttribute = moduleNode.Attributes["LayoutPageFileName"];

                                            if (layoutAttribute != null)
                                            {
                                                // Walk the layout pages looking for a match
                                                foreach (PageLayout layout in layouts)
                                                {
                                                    // See if this is the correct one
                                                    if (string.Compare(layout.Name, layoutAttribute.InnerText, true) == 0)
                                                    {
                                                        targetLayout = layout;
                                                        break;
                                                    }
                                                }
                                            }

                                            // Did we find a match
                                            if (targetLayout != null)
                                            {
                                                // Go find all of the pages in the configuration file
                                                XmlNodeList pageNodes = doc.SelectNodes("sp:Elements/sp:Module/sp:Page", nsm);
                                                
                                                // Walk the list and create pages
                                                foreach (XmlNode pageNode in pageNodes)
                                                {
                                                    CreatePage(web, targetLayout, pageNode, nsm, baseWebPartPath);
                                                }
                                            }
                                            else
                                            {
                                                //Log no layout page
                                            }
                                        }
                                        else
                                        {
                                            // Log no module in config file
                                        }
                                    }
                                    else
                                    {
                                        // Log web is not a publishing web
                                    }
                                }
                                catch (XmlException)
                                {
                                    // Log the xml exception
                                }
                            }
                            else
                            {
                                // Log no config file
                            }
                        }
                    }
                }
            }
        }
        #endregion

    }
}
